#include <errno.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/ipc.h>
#include <sys/shm.h> 
#include <sys/sem.h>
#include <signal.h>

#include "quadrdefs.h"
//#include "fns.h"

char *buf = NULL;
int semid, shmid;

/* To find the integration accurately split the intervals of the integration into n intervals and find out summation of it.
   And then apply trapeziodal rule on it */
double calc_sum(FnP fnPtr, double lo_bound, double hi_bound, int n_intervals){
	int k;
	double sum_result = 0;

	for (k = 1; k <= (n_intervals - 1); k++){
		sum_result = sum_result + fnPtr(lo_bound + k * (hi_bound - lo_bound) / n_intervals);
	}
	return sum_result;
}

/* Funtion for calulating integral by using trapezoidal rule */
double trapezoidal(FnP fnPtr, double lo_bound, double hi_bound, int n_intervals){

	double trapezoidal_result;  /* Store trapezoidal result */
	double sum_result;    /* Store summation result */

	/*Calculate the summation */
	sum_result = calc_sum (fnPtr, lo_bound, hi_bound, n_intervals);

	/*Compute actual trapezoidal result */
	trapezoidal_result = ((hi_bound - lo_bound) / n_intervals) * ( (fnPtr(hi_bound) + fnPtr(lo_bound))/2 + sum_result);
	return trapezoidal_result;
}



/* Pass 0/1 as 2nd argument to checkNum funtion to check if it is double or not */
int checkNum (char *strPtr, int isDouble){
	int i = 0;
	int flag = 0;

	if (strPtr[0] == '.'){
		return 1;
	}

	for (i = 0; i < strlen (strPtr); i++){
		if (strPtr[i] == '.'){
			if (flag == 0)
				flag = 1;
			else
				return 1;
		}
	}

	for (i = 0; i < strlen (strPtr); i ++){
		if (isDouble == 1){

			if (!isdigit((int)strPtr[i]) && strPtr[i] != 'e' && strPtr[i] != 'E' && strPtr[i] != '+' && strPtr[i] != '.' &&
					strPtr[i] != '-'){
				return 1;
			}
		}
		else{
			if (!isdigit((int)strPtr[i])){
				return 1;
			}

		}
		/* Check for same operator does not occur. eg. 12++1, 12-+1, etc */
		if (strPtr[i] == '+' || strPtr[i] == '.' || strPtr[i] == '-'){
			if (!isdigit ((int)strPtr[i+1] )) {
				return 1;
			}
			if ( i != 0 && strPtr[i - 1] == ' ' && strPtr[i-1] == 'e'){
				return 1;
			}
		}
		if (strPtr[i] == 'e' || strPtr[i] == 'E'){
			if (!isdigit((int)strPtr[i+1]) && strlen (strPtr) <= i + 1){
				return 1;
			}
		}
	}
	return 0;
}

Params validate_input(char *line){

	Params prm;

	char *strPtr = NULL, *pEnd = NULL;
	int i = 0, flag = 1;

	strPtr = strtok (line, " \n");

	if(strcmp(strPtr, "quit") == 0){
		prm.error = 1;
		return prm;
	}

	while (strPtr != NULL){

		if ( i == 0){
			//Check if the funtion is available in fns[]
			if ( getFnForString (strPtr)){
				prm.fnName = (char *)malloc (sizeof (char) * strlen (strPtr));
				strcpy (prm.fnName, strPtr);
			}
			else{
				//fprintf (stderr, "\nERROR: Function \"%s\" is not defined\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 1){
			/* Pass 1 as 2nd argument to checkNum funtion if the entered value is double */
			if (checkNum(strPtr, 1) == 0){
				prm.lo_bound = strtod (strPtr, &pEnd);
			}
			else{
				//fprintf (stderr, "\nERROR: Invalid LO_BOUND value : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 2){
			if (checkNum (strPtr, 1) == 0){
				prm.hi_bound = strtod (strPtr, &pEnd);
			}
			else{
				//fprintf (stderr, "\nERROR: Invalid HI_BOUND value : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i == 3){
			/* Pass 0 as 2nd argument to checkNum funtion if the entered value is not double */
			if (checkNum (strPtr, 0) == 0){
				prm.n_intervals = atoi (strPtr);
			}
			else{
				//fprintf (stderr, "\nERROR: Invalid N_INTERVALS : %s\n", strPtr);
				flag = 1;
				prm.error = -1;
				return prm;
			}
		}

		if ( i >= 4){
			//fprintf (stderr, "\nERROR: Illegal input %s\n", strPtr);
			flag = 1;
			prm.error = -1;
			return prm;
		}

		i++;
		/* Read next argument from line by using strtok */
		strPtr = strtok (NULL, " \n");
	}

	i--;
	if (i != 3){
		//fprintf (stderr, "\nERROR: Insufficient values provided\n");
		prm.error = -1;
		return prm;
	}

	return prm;
}


void signal_handler(int signal) {

	union semun un;
	switch(signal) {

		case SIGTERM:
			fprintf (stderr, "\nQUADRD: Got 'kill' (SIGTERM) signal. \nQUADRD daemon is now exiting...\n ");

			if (shmdt(buf) == -1)
			{
				fprintf(stderr, "ERROR: shmdt() failed\n");
				exit(1);
			}
			if (shmctl(shmid, IPC_RMID, NULL) == -1)
			{
				fprintf(stdout,"ERROR: shmctl() failed\n");
				exit(1);
			}

			if (semctl (semid, 0, IPC_RMID, un) == -1){
				fprintf (stderr, "ERROR: semctl() remove 0 ID failed\n");
				exit(1);
			}
			exit(0);
	}
}


int getClientRequest (char *dir){

	double result = 0;
	key_t key;
	short sarr[3];
	struct sembuf sops[3];
	union semun un;



	/*line[0] = '\0';
	  if ((readCnt = read (STDIN_FILENO,line, BUFSIZE)) == -1){
	  fprintf (stderr, "ERROR: Unable to read line\n");
	  exit(1);
	  }*/

	key = ftok("fns.h",0);
	if (key == (key_t) - 1){
		fprintf (stderr, "ERROR: ftok failed\n");
		exit(1);
	}

	setsid ();
	chdir (dir);
	umask (0);


	/* get a semaphore set identifier */
	semid = semget( key, 3, 0666 | IPC_CREAT | IPC_EXCL );
	//semid = semget( key, 2, 0666 | IPC_CREAT );
	if ( semid == -1 )
	{
		fprintf(stderr,"ERROR: semget() failed\n");
		exit(-1);
	}

	sarr[0] = 0;
	sarr[1] = 0;
	sarr[2] = 0;

	un.val = 0;
	if (semctl (semid, 0, SETVAL, un) == -1) {
		fprintf (stderr, "ERROR: semctl() client  initialization failed\n");
		exit(1);
	}

	if (semctl (semid, 2, SETVAL, un) == -1) {
		fprintf (stderr, "ERROR: semctl() initialization failed\n");
		exit(1);
	}

	un.val = 0;
	if (semctl (semid, 1, SETVAL, un) == -1) {
		fprintf (stderr, "ERROR: semctl() shared memory initialization failed\n");
		exit(1);
	}

	/* allocates a shared memory segment */
	if ((shmid = shmget(key, MAXSZ, IPC_CREAT | 0666)) < 0){
		fprintf (stderr, "ERROR: Unable to create shared memory\n");
		exit(1);
	}

	if ((buf = shmat (shmid, NULL, 0666)) < 0){
		fprintf (stderr, "ERROR: Unable to attach shared memory\n");
		exit(1);
	}

	signal(SIGTERM,signal_handler); /* catch kill signal */

	while (1){

		Params prm;


		/* Wait for client's request to serve him */
		sops[0].sem_num = 0;
		sops[0].sem_op = -1;
		sops[0].sem_flg = SEM_UNDO;

		if (semop (semid, sops, 1) < 0){
			fprintf (stderr, "ERROR: semop() failed \n");
			exit(1);
		}

		prm = validate_input(buf);

		if (prm.error == -1){
			strcpy (buf, "Invalid Arguments\n");

		}
		else
		{

			result = trapezoidal(getFnForString(prm.fnName), prm.lo_bound, prm.hi_bound, prm.n_intervals);

			//fprintf (stdout,"Result : %lg\n", result);
			buf[0] = '\0';

			sprintf(buf, "%lg\n",result);
		}

		/* Release the client lock */
		sops[0].sem_num = 2;
		sops[0].sem_op = 1;
		sops[0].sem_flg = SEM_UNDO;

		if (semop (semid, sops, 1) < 0){
			fprintf (stderr, "ERROR: semop() failed \n");
			exit(1);
		}
	}

	return 0;
}

	int 
main(int argc, const char *argv[])
{
	pid_t pid;
	int retVal;
	char *dir = NULL;

	if (argc != 2) {
		fprintf(stderr, "usage: %s DIR\n", argv[0]); exit(1);
	}

	dir = (char *) malloc (sizeof(char) * strlen(argv[1]) + 1);
	dir [strlen (argv[1]) + 1] = '\0';

	if ((pid = fork()) < 0){
		fprintf (stderr, "failed to create child process\n");
		exit (1);
	}
	if (pid != 0){
		//fprintf (stdout,"Exiting Parent\n");
		exit (0); /*Exit Parent so that client can act as daemon */
	}
	else{
		if (pid == 0){

			if (mkdir (argv[1], 0755) == -1){
				if (errno != EEXIST){
					fprintf (stderr,"Can't create directory\n");
					exit(1);
				}
			}
			fprintf (stdout, "Daemon PID : %ld\n",(long)getpid());

			retVal = getClientRequest (dir);
		}
	}
	return 0;
}
